use anyhow::{Error, Result};
use clap::{Parser, Subcommand};
use colored::*;
use std::{
    convert::{TryFrom, TryInto},
    path::PathBuf,
};
use workspace::ManifestPath;
use xargo_lib::Verbosity;

mod cmd;
mod utils;
mod workspace;
#[derive(Parser)]
#[command(bin_name = "cargo-liquid")]
#[command(name = "liquid")]
pub struct Opts {
    #[command(subcommand)]
    cmd: Command,
}

#[derive(Subcommand)]
enum Command {
    /// Sets up and creates a new liquid project.
    New {
        /// The type of the project, must be `contract` or `collaboration`.
        ty: String,
        /// The name of the newly created project.
        name: String,
        /// The optional target directory for the newly created project.
        #[clap(short, long, value_parser)]
        target_dir: Option<PathBuf>,
    },

    /// Builds the project.
    Build {
        #[clap(flatten)]
        verbosity_flags: VerbosityFlags,
        /// Indicates using GM mode or not.
        #[clap(short, long)]
        gm: bool,
        /// Indicates the manifest to use, must be a Cargo.toml file.
        #[clap(short, long)]
        manifest_path: Option<PathBuf>,
        #[clap(flatten)]
        analysis_flags: AnalysisFlags,
        /// If this flag is set, the analysis process will produce the whole call graph in dot format.
        #[clap(short, long)]
        dump_cfg: Option<PathBuf>,
    },

    /// Rename the project, you need to update the name in the cargo-liquid dir.
    Rename {
        /// The project old name.
        old_name: String,
        /// The project new name.
        new_name: String,
    },
}

#[derive(clap::Args)]
struct VerbosityFlags {
    /// No output printed to stdout.
    #[clap(short, long)]
    quiet: bool,
    /// Uses verbose output.
    #[clap(long)]
    verbose: bool,
}

#[derive(Copy, Clone)]
enum VerbosityBehavior {
    Quiet,
    Verbose,
}

#[derive(clap::Args)]
struct AnalysisFlags {
    /// If this flag is set, the analysis process will be skipped unconditionally.
    #[clap(long, value_parser, default_value_t = true)]
    skip_analysis: bool,
}

#[derive(PartialEq, Eq, Copy, Clone)]
enum AnalysisBehavior {
    Enforce,
    Skip,
}
impl From<VerbosityBehavior> for Verbosity {
    fn from(vb: VerbosityBehavior) -> Self {
        match vb {
            VerbosityBehavior::Quiet => Verbosity::Quiet,
            VerbosityBehavior::Verbose => Verbosity::Verbose,
        }
    }
}

impl TryFrom<&VerbosityFlags> for VerbosityBehavior {
    type Error = Error;

    fn try_from(value: &VerbosityFlags) -> Result<Self, Self::Error> {
        match (value.quiet, value.verbose) {
            (true, false) => Ok(VerbosityBehavior::Quiet),
            (false, false) => Ok(VerbosityBehavior::Quiet),
            (false, true) => Ok(VerbosityBehavior::Verbose),
            (true, true) => anyhow::bail!("Cannot pass both --quiet and --verbose flags"),
        }
    }
}

impl TryFrom<&AnalysisFlags> for AnalysisBehavior {
    type Error = Error;

    fn try_from(value: &AnalysisFlags) -> Result<Self, Self::Error> {
        if value.skip_analysis {
            Ok(AnalysisBehavior::Skip)
        } else {
            Ok(AnalysisBehavior::Enforce)
        }
    }
}
fn main() {
    let opts = Opts::parse();

    match exec(opts.cmd) {
        Ok(msg) => println!("{}", msg.bold()),
        Err(err) => eprintln!("{} {}", "ERROR:".bright_red().bold(), format!("{:?}", err)),
    }
}

fn exec(cmd: Command) -> Result<String> {
    match &cmd {
        Command::New {
            ty,
            name,
            target_dir,
        } => cmd::execute_new(ty, name, target_dir.as_ref()),
        Command::Build {
            verbosity_flags,
            gm,
            manifest_path,
            dump_cfg,
            analysis_flags,
        } => cmd::execute_build(
            manifest_path
                .as_ref()
                .map_or(Default::default(), |manifest_path| {
                    ManifestPath::new(manifest_path).expect("invalid manifest path")
                }),
            *gm,
            verbosity_flags.try_into()?,
            analysis_flags.try_into()?,
            dump_cfg,
        ),
        Command::Rename { old_name, new_name } => cmd::execute_rename(old_name, new_name),
    }
}
